import java.util.HashMap;
import java.util.Stack;

public class Expression implements YakaConstants {

	/** Table des transitions qui associe a chaque operateur
	 * les types d'entrees et de retour associes.
	 */
	public static HashMap<Integer, HashMap<Integer, Integer>> transitionsOperations;
	
	/** 
	 * Pile des types d'operandes de l'expression courante.
	 */
	private Stack<Integer> typesOperandes;
	
	/** 
	 * Pile des operateurs de l'expression courante.
	 */
	private Stack<Integer> operateurs;
	
	/**
	 * Type de l'ident en cours d'affectation.
	 * Permet de controler la coherence du type.
	 */
	private int typeIdentAffecte;
	
	/**
	 * Nom de l'ident en cours d'affectation.
	 * Permet de retrouver l'offset correspondant.
	 */
	private String dernierIdentAffecte;
	
	/**
	 * Pile qui permet de sauvegarder l'indice de l'argument de la fonction en cours d'appel.
	 * La pile permet de gerer l'imbrication des fonctions.
	 */
	private Stack<Integer> controleArguments;
	
	public Expression() {
		typesOperandes = new Stack<Integer>();
		operateurs = new Stack<Integer>();
		controleArguments = new Stack<Integer>();
	}
	
	
	public static void initialiserTransitionsAutorisees()
	{
		transitionsOperations = new HashMap<Integer, HashMap<Integer,Integer>>();
		
		HashMap<Integer, Integer> t1 = new HashMap<Integer, Integer>();  
		t1.put(TYPE_ENTIER, TYPE_ENTIER);
		t1.put(TYPE_BOOLEEN, TYPE_ERREUR);
		t1.put(TYPE_ERREUR, TYPE_ERREUR);
		transitionsOperations.put(PLUS, t1);
		transitionsOperations.put(MOINS, t1);
		transitionsOperations.put(NEGATIF, t1);
		transitionsOperations.put(FOIS, t1);
		transitionsOperations.put(DIVISE, t1);		
		  
		HashMap<Integer, Integer> t2 = new HashMap<Integer, Integer>();
		t2.put(TYPE_ENTIER, TYPE_BOOLEEN);
		t2.put(TYPE_BOOLEEN, TYPE_ERREUR);
		t2.put(TYPE_ERREUR, TYPE_ERREUR);
		transitionsOperations.put(INFERIEUR, t2);
		transitionsOperations.put(SUPERIEUR, t2);
		transitionsOperations.put(INFERIEUR_OU_EGAL, t2);
		transitionsOperations.put(SUPERIEUR_OU_EGAL, t2);
		
		HashMap<Integer, Integer> t3 = new HashMap<Integer, Integer>();  
		t3.put(TYPE_ENTIER, TYPE_BOOLEEN);
		t3.put(TYPE_BOOLEEN, TYPE_BOOLEEN);
		t3.put(TYPE_ERREUR, TYPE_ERREUR);
		transitionsOperations.put(EGAL, t3);
		transitionsOperations.put(DIFFERENT, t3);
		
		HashMap<Integer, Integer> t4 = new HashMap<Integer, Integer>();  
		t4.put(TYPE_ENTIER, TYPE_ERREUR);
		t4.put(TYPE_BOOLEEN, TYPE_BOOLEEN);
		t4.put(TYPE_ERREUR, TYPE_ERREUR);
		transitionsOperations.put(ET, t4);
		transitionsOperations.put(OU, t4);
	}
	
	public void empilerOperateur(int operateur) {
		operateurs.push(new Integer(operateur));
	}
	
	public void empilerTypeOperande(int type) {
		typesOperandes.push(new Integer(type));
	}
	
	public void faitOperation()
	{
		// Controle du type
		int operateur = operateurs.peek();
		controlerOperation();
		
		// Application de l'operation
		
		switch(operateur)
		{
			case PLUS:
				Yaka.yvm.iadd();
				break;
			case MOINS:
				Yaka.yvm.isub();
				break;
			case FOIS:
				Yaka.yvm.imul();
				break;
			case DIVISE:
				Yaka.yvm.idiv();
				break;
			case NON:
				Yaka.yvm.inot();
				break;
			case NEGATIF:
				Yaka.yvm.ineg();
				break;
			case OU:
				Yaka.yvm.ior();
				break;
			case ET:
				Yaka.yvm.iand();
				break;
			case INFERIEUR:
				Yaka.yvm.iinf();
				break;
			case SUPERIEUR:
				Yaka.yvm.isup();
				break;
			case INFERIEUR_OU_EGAL:
				Yaka.yvm.iinfegal();
				break;
			case SUPERIEUR_OU_EGAL:
				Yaka.yvm.isupegal();
				break;
			case EGAL:
				Yaka.yvm.iegal();
				break;
			case DIFFERENT:
				Yaka.yvm.idiff();
				break;
		}
	}
	
	public void debug() {
		Token token = Yaka.token;
		System.out.println("Ligne " + token.beginLine + " (" + token.beginColumn + ") a ligne " + token.endLine + " (" + token.endColumn + ")");
	}
	
	public void controlerOperation()
	{
		int operateur = operateurs.pop();
		int type1 = typesOperandes.pop();
		int type2 = typesOperandes.pop();
		
		if (type1 == type2)
		{
			typesOperandes.push( transitionsOperations.get(operateur).get(type1) );
		}			
		else {
			System.out.println("; Operation entre deux types incompatibles (" + Ident.typeToString(type1) + ", " + Ident.typeToString(type2) + ").");
			debug();
			typesOperandes.push(TYPE_ERREUR);
		}
			
	}
	
	public void controlerAffectation() {
		int typeIdentValeur = typesOperandes.pop();
		
		if (typeIdentAffecte != typeIdentValeur) {
			System.out.println("; Affectation entre deux types differents (" + Ident.typeToString(typeIdentAffecte) + ", " + Ident.typeToString(typeIdentValeur) + ").");
			debug();
		}
	}

	
	public void controlerRetourFonction(int typeFonction) {
		int type = typesOperandes.peek();
		
		if (type != typeFonction) {
			System.out.println("; Retour de fonction incompatible dans la declaration(" + Ident.typeToString(type) + ", " + Ident.typeToString(typeFonction) + ").");
			debug();
		}
	}
	
	public void controleBoolean(){
		int typeIdentValeur = typesOperandes.pop();
		
		if (TYPE_BOOLEEN != typeIdentValeur) {
			System.out.println("; expression doit etre de type Boolean (" + Ident.typeToString(typeIdentValeur) + ").");
			debug();
		}
	}
	
	public void setAffectation(String ident) {
		dernierIdentAffecte = ident;
		
		if (Yaka.tableIdents.existeIdent(ident))
			typeIdentAffecte = Yaka.tableIdents.chercheIdent(ident).getType();
		else {
			System.out.println("; Variable/Constante non definie.");
			debug();
			typeIdentAffecte = TYPE_ERREUR;
		}
	}
	
	public void retourFonction(){
		Fonction f = Yaka.tableIdents.chercheFonction(Yaka.yvm.pileAppelsFonctions.peek());		

		typesOperandes.push(f.getType());
		
		controleArguments.pop();
	}
	
	public void controlerArgument(){
		int type = typesOperandes.peek();
		Fonction f = Yaka.tableIdents.chercheFonction(Yaka.yvm.pileAppelsFonctions.peek());		

		if (type != f.getParamType(controleArguments.peek())) {
			System.out.println("; Param de fonction incompatible avec l'expression (" + Ident.typeToString(type) + ", " + Ident.typeToString(f.getParamType(controleArguments.peek())) + ").");
			debug();
		}
		controleArguments.push(controleArguments.pop()+1);
	}
	
	public String getDernierIdentAffecte() {
		return dernierIdentAffecte;
	}
	
	public void addControleArguments(){
		controleArguments.push(0);
	}
	
	
	public int getType(){
		return typesOperandes.peek();
	}
}
